/**
 * 
 */
package edu.pw.treegrid.server.reportmodel;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import edu.pw.treegrid.server.classmodel.DomainClass;
import edu.pw.treegrid.server.filter.Filter;
import edu.pw.treegrid.server.filter.FilterDeserializer;
import edu.pw.treegrid.server.service.XMLMarshaller;
import xalan.xml.XMLUtils;

/** 
 * @author Xalan
 */
public class Report {
	private static Logger log = Logger.getLogger("xalan.reports");
	
	public static final String COLUMNS_ELEMENT = "columns";
	public static final String REF_ATTRIBUTE = "ref";
	public static final String MAIN_CLASS_ELEMENT = "mainClass";
	public static final String NAME_ELEMENT = "name";
	public static final String DESCRIPTION_ELEMENT = "description";
	public static final String COUNT_ELEMENT = "count";
	public static final String REPORT_ELEMENT = "report";
	public static final String ID_ATTRIBUTE = "id";
	public static final String FILTER_ELEMENT = "filter";
	public static final String HIERARCHIES_ELEMENT = "hierarchies";
	public static final String HIERARCHY_ELEMENT = "hierarchy";
	public static final String NAME_COLUMN_ELEMENT = "nameColumn";
	
	public Report() {
		initColumns();
	}
	
	private String id;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	private DomainClass mainClass;

	public DomainClass getMainClass() {
		return mainClass;
	}

	public void setMainClass(DomainClass mainClass) {
		this.mainClass = mainClass;
	}
	
	private String description = "";
	
	private boolean countVisible = true;
	
	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public boolean isCountVisible() {
		return countVisible;
	}

	public void setCountVisible(boolean countVisible) {
		this.countVisible = countVisible;
	}

	private List<ReportColumn> columns;

	public List<ReportColumn> getColumns() {
		return columns;
	}
	
	private Filter filter;

	public void setFilter(Filter filter) {
		this.filter = filter;
	}
	
	public Filter getFilter() {
		return filter;
	}

	public void addColumn(ReportColumn column) {
		if(this.columns.contains(column)) {
			throw new RuntimeException("this.columns.contains(column): " + column);
		}
		this.columns.add(column);
		column.setReport(this);
	}

	private Set<ReportHierarchy> standardHierarchies = new HashSet<ReportHierarchy>();
	
	public void addStandardHierarchy(String hierarchyName, List<ReportColumn> columns) {
		ReportHierarchy h = new ReportHierarchy();
		h.setHierarchyName(hierarchyName);
		h.setColumns(columns);
		this.standardHierarchies.add(h);
	}
	
	public Set<ReportHierarchy> getStandardHierarchies() {
		return this.standardHierarchies;
	}
	
	private ReportColumn nameColumn;
	
	public ReportColumn getNameColumn() {
		return nameColumn;
	}

	public void setNameColumn(ReportColumn nameColumn) {
		this.nameColumn = nameColumn;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Report other = (Report) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	public ReportColumn getColumnByName(String columnName) {
		for(ReportColumn rc: columns) {
			if(rc.getName().equals(columnName)) {
				return rc;
			}
		}
		return null;
	}

	public Element serializeToXML(Document document) {
		Element reportElement = document.createElement(REPORT_ELEMENT);
		reportElement.setAttribute(ID_ATTRIBUTE, id);
		
		Element nameElement = document.createElement(NAME_ELEMENT);
		nameElement.setTextContent(name);
		reportElement.appendChild(nameElement);
		
		Element mainClassElement = document.createElement(MAIN_CLASS_ELEMENT);
		mainClassElement.setAttribute(REF_ATTRIBUTE, mainClass.getId());
		reportElement.appendChild(mainClassElement);		
		
		Element descElement = document.createElement(DESCRIPTION_ELEMENT);
		descElement.setTextContent(description);
		reportElement.appendChild(descElement);
		
		Element countElement = document.createElement(COUNT_ELEMENT);
		countElement.setTextContent(Boolean.toString(countVisible));
		reportElement.appendChild(countElement);
		
		Element nameColumnElement = document.createElement(NAME_COLUMN_ELEMENT);
		nameColumnElement.setAttribute(REF_ATTRIBUTE, (nameColumn != null ? nameColumn.getName() : ""));
		reportElement.appendChild(nameColumnElement);
		
		Element columnsElement = document.createElement(COLUMNS_ELEMENT);
		for(ReportColumn rc : columns) {
			if(rc.isSerialize()) {
				columnsElement.appendChild(rc.serializeToXML(document));
			}
		}
		
		reportElement.appendChild(columnsElement);
		
		if(filter != null) {
			Element filterElement = document.createElement(FILTER_ELEMENT);
			filterElement.appendChild(filter.serializeToXML(document));
			reportElement.appendChild(filterElement);
		}
		
		if(!standardHierarchies.isEmpty()) {
			Element hierarchiesElement = document.createElement(HIERARCHIES_ELEMENT);
			
			for(ReportHierarchy h: this.standardHierarchies) {
				List<ReportColumn> columns = h.getColumns();
				Element hierarchyElement = document.createElement(HIERARCHY_ELEMENT);
				hierarchyElement.setAttribute(NAME_ELEMENT, h.getHierarchyName());
				
				for(ReportColumn column: columns) {
					Element columnElement = document.createElement(ReportColumn.COLUMN_ELEMENT);
					columnElement.setAttribute(REF_ATTRIBUTE, column.getName());
					hierarchyElement.appendChild(columnElement);
				}
				hierarchiesElement.appendChild(hierarchyElement);
			}
			reportElement.appendChild(hierarchiesElement);
		}
		
		return reportElement;
	}

	public void deserializeFromXML(Element e, XMLMarshaller marshaller) {
		this.id = e.getAttribute(ID_ATTRIBUTE);
		this.name = XMLUtils.getElementsText(e, NAME_ELEMENT);
		String classId = XMLUtils.getChildElementAttribute(e, MAIN_CLASS_ELEMENT, REF_ATTRIBUTE);
		this.mainClass = marshaller.getClassById(classId);
		if(this.mainClass == null) {
			log.warn("Klasa nie istnieje [" + classId + "]");
		}
		this.description = XMLUtils.getElementsText(e, DESCRIPTION_ELEMENT);
		this.countVisible = Boolean.parseBoolean(XMLUtils.getElementsText(e, COUNT_ELEMENT));
		initColumns();
		
		NodeList columnsList = XMLUtils.getFirstElement(e, COLUMNS_ELEMENT).getChildNodes();
		for(int i=0;i<columnsList.getLength();++i) {
			Node n = columnsList.item(i);
			if(n instanceof Element) {
				Element ae = (Element)n;
				if(ae.getTagName().equals(ReportColumn.COLUMN_ELEMENT)) {
					ReportColumn rc = ReportColumn.newColumn(ae, marshaller);
					rc.setReport(this);
					addColumn(rc);
				}
			}
		}
		
		String name = XMLUtils.getChildElementAttribute(e, NAME_COLUMN_ELEMENT, REF_ATTRIBUTE);
		if(name.length() > 0) {
			this.nameColumn = getColumnByName(name);
		}
		
		NodeList filtersList = e.getElementsByTagName(FILTER_ELEMENT);
		if(filtersList.getLength() > 0) {
			Element filterElement = (Element)filtersList.item(0);
			NodeList childs = filterElement.getChildNodes();
			for(int i=0;i<childs.getLength();++i) {
				Node c = childs.item(i);
				if(c instanceof Element) {
					setFilter(FilterDeserializer.newFilter((Element)c, this));
					break;
				}
			}
		}
		
		deserializeHierarchiesFromXML(e);
	}

	private void deserializeHierarchiesFromXML(Element e) {
		NodeList hierarchiesList = e.getElementsByTagName(HIERARCHIES_ELEMENT);
		if(hierarchiesList.getLength() > 0) {
			Element hierarchiesElement = (Element)hierarchiesList.item(0);
			NodeList childs = hierarchiesElement.getElementsByTagName(HIERARCHY_ELEMENT);
			for(int i=0;i<childs.getLength();++i) {
				Node c = childs.item(i);
				if(c instanceof Element) {
					Element hierarchyElement = (Element)c;
					String hierarchyName = hierarchyElement.getAttribute(NAME_ELEMENT);
					List<ReportColumn> columns = deserializeHierarchyFromXML(hierarchyName, hierarchyElement);
					this.addStandardHierarchy(hierarchyName, columns);
				}
			}
		}
	}

	private List<ReportColumn> deserializeHierarchyFromXML(String hierarchyName, Element hierarchyElement) {
		List<ReportColumn> columns = new LinkedList<ReportColumn>();
		NodeList childs = hierarchyElement.getElementsByTagName(ReportColumn.COLUMN_ELEMENT);
		for(int i=0;i<childs.getLength();++i) {
			Node c = childs.item(i);
			if(c instanceof Element) {
				Element columnElement = (Element)c;
				String columnId = columnElement.getAttribute(REF_ATTRIBUTE);
				ReportColumn column = this.getColumnByName(columnId);
				if(column == null) {
					log.warn("Kolumna [" + columnId + "] w hierarchii [" + hierarchyName + "] nie istnieje w raporcie [" + this.name + "].");
				}
				columns.add(column);
			}
		}
		return columns;
	}

	private void initColumns() {
		this.columns = new LinkedList<ReportColumn>();
		addColumn(new CountColumn());
	}

}
